#!/usr/bin/env python

from pprint import pprint

CHARS_PER_LINE = 40
CYCLES_PER_STROBE = 8
START_CHAR = 2
SKIP_FIRST_LEFT_HALF = True

insns = []

# what temporary registers do we have available?
registers = ['r16', 'r17', 'r18', 'r19', 'r20']
setupcode = """
ld     ZL, X+ ;get index 0
;ld    ZL, X+
elpm   ZL, Z ;get pattern 0
;elpm  ZL, Z
;;elpm ZL, Z
ld     ZH, Y+ ;get color 0
lpm    r16, Z ;get pixels 0L
;lpm   r16, Z
;;lpm  r16, Z
swap   ZL
lpm    r17, Z ;get pixels 0R
;lpm   r17, Z
;;lpm  r17, Z

in     ZH, IO_(GPIOR0)
ld     ZL, X+ ;get index 1
;ld    ZL, X+
elpm   ZL, Z ;get pattern 1
;elpm  ZL, Z
;;elpm ZL, Z
ld     ZH, Y+ ;get color 1
lpm    r18, Z ;get pixels 1L
;lpm   r18, Z
;;lpm  r18, Z
swap   ZL
lpm    r19, Z ;get pixels 1R
;lpm   r19, Z
;;lpm  r19, Z

in     ZH, IO_(GPIOR0)
ld     ZL, X+ ;get index 2
;ld    ZL, X+
elpm   ZL, Z ;get pattern 2
;elpm  ZL, Z
;;elpm ZL, Z
ld     ZH, Y+ ;get color 2
lpm    r20, Z ;get pixels 2L
;lpm   r20, Z
;;lpm  r20, Z
swap   ZL
lpm    ZH, Z ; get pixels 2R
;lpm   ZH, Z"""
# what are the initial register values at the end of `setupcode`?
regmap = {'r16': '0L', 'r17': '0R', 'r18': '1L', 'r19': '1R', 'r20': '2L', 'ZH': '2R'}


def next_register():
    while True:
        for r in registers:
            yield r


def has_value_in_reg(pixels, rmap):
    for k,v in rmap.iteritems():
        if v == pixels:
            return k
    return None


def find_insert_pos(pixels, startcycle, endcycle):
    for c in xrange(startcycle, endcycle):
        reg = has_value_in_reg(pixels, insns[c][1])
        if not insns[c][0].startswith(';') and reg is not None:
            return c, reg
    return None, None

regs = next_register()

# generate code without adding any `out` instructions
for i in xrange(START_CHAR, CHARS_PER_LINE):
    if not SKIP_FIRST_LEFT_HALF:
        insns.append(('in     ZH, IO_(GPIOR0)', dict(regmap)))
        insns.append(('ld     ZL, X+ ;get index {}'.format(i), dict(regmap)))
        insns.append((';ld    ZL, X+', dict(regmap)))
        insns.append(('elpm   ZL, Z ;get pattern {}'.format(i), dict(regmap)))
        insns.append((';elpm  ZL, Z', dict(regmap)))
        insns.append((';;elpm ZL, Z', dict(regmap)))
        insns.append(('ld     ZH, Y+ ;get color {}'.format(i), dict(regmap)))
        insns.append((';ld    ZH, Y+', dict(regmap)))
        reg1 = next(regs)
        insns.append(('lpm    {}, Z ;get pixels {}L'.format(reg1,i), dict(regmap)))
        insns.append((';lpm   {}, Z'.format(reg1), dict(regmap)))
        insns.append((';;lpm  {}, Z'.format(reg1), dict(regmap)))
        regmap[reg1] = '{}L'.format(i)
        reg2 = next(regs)
        insns.append(('swap   ZL', dict(regmap)));
        insns.append(('lpm    {}, Z ;get pixels {}R'.format(reg2,i), dict(regmap)))
        insns.append((';lpm   {}, Z'.format(reg2), dict(regmap)))
        insns.append((';;lpm  {}, Z'.format(reg2), dict(regmap)))
        regmap[reg2] = '{}R'.format(i)
    else:
        SKIP_FIRST_LEFT_HALF = False
        firstreg = next(regs)
        insns.append((';;lpm ZH,  Z', dict(regmap)))
        insns.append(('mov    r16, ZH', dict(regmap)))
        regmap[firstreg] = regmap['ZH']
        del regmap['ZH']

# insert a dummy instruction to record the last regmap update
insns.append(('', dict(regmap)))

# now, insert `out` instructions
for i in xrange(0, CHARS_PER_LINE):
    start = (CYCLES_PER_STROBE*2)*i
    deadline1 = start+CYCLES_PER_STROBE
    deadline2 = start+(CYCLES_PER_STROBE*2)
    pixels1 = '{}L'.format(i)
    pixels2 = '{}R'.format(i)
    # print '---------------- {}'.format(pixels1)
    # pprint(insns[start:deadline1], width=160)
    pos1, reg1 = find_insert_pos(pixels1, start, deadline1)
    # print pos1, reg1
    if pos1 is not None:
        insns.insert(pos1, ('out    IO_(PORTC), {} ;out {}'.format(reg1,pixels1), {}))
    else:
        print 'deadline missed for {}'.format(pixels1)
        break
    # print '---------------- {}'.format(pixels2)
    # pprint(insns[deadline1:deadline2], width=160)
    pos2, reg2 = find_insert_pos(pixels2, deadline1, deadline2)
    # print pos2, reg2
    if pos2 is not None:
        insns.insert(pos2, ('out    IO_(PORTC), {} ;out {}'.format(reg2,pixels2), {}))
    else:
        print 'deadline missed for {}'.format(pixels2)
        break

print ";--- this code was autogenerated... lasciate ogni speranza, voi ch'entrate! ---"
setupcodelines = setupcode.splitlines()
setupcodelines = filter(lambda l: len(l)>0 and not l.startswith(';-'), setupcodelines)
cyclenum = 0
for l in setupcodelines:
    print '{:<32} ;(Mcy={}, Scy={})'.format(l, cyclenum, cyclenum % CYCLES_PER_STROBE);
    cyclenum += 1

code = [i for i,_ in insns]
for i, c in enumerate(code):
    tilenum = i/(CYCLES_PER_STROBE*2)
    tilehalf = (i/(CYCLES_PER_STROBE)) & 1
    if len(c) > 0:
        header = ''
        if (i % CYCLES_PER_STROBE) == 0:
            header = ' ;-------- tile {}{}'.format(tilenum, 'R' if tilehalf else 'L')
        print '{:<32} ;(Mcy={}, Scy={}){}'.format(c, cyclenum+i, (cyclenum+i) % CYCLES_PER_STROBE, header)
